 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.update.internal.verifier;

 import java.io.File ;
 import java.io.IOException ;
 import java.util.ArrayList ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.jar.JarFile ;
 import java.util.zip.ZipException ;

 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.osgi.internal.provisional.verifier.*;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.update.core.*;
 import org.eclipse.update.internal.core.Messages;
 import org.eclipse.update.internal.core.UpdateCore;

 /**
  * The JarVerifier will check the integrity of the JAR.
  * If the Jar is signed and the integrity is validated,
  * it will check if one of the certificate of each file
  * is in one of the keystore.
  *
  */

 public class CertVerifier extends Verifier {

     private CertVerificationResult result;
     private boolean acceptUnsignedFiles;
     private IProgressMonitor monitor;
     private File jarFile;
     private CertificateVerifierFactory factory;
     private List trustedCertificates;

     /*
      * Default Constructor
      */
     public CertVerifier(CertificateVerifierFactory factory) {
         this.factory = factory;
         initialize();
     }


     /*
      *
      */
     private void initialize() {
         result = null;
         acceptUnsignedFiles = false;
     }

     /*
      * init
      */
     private void init(IFeature feature, ContentReference contentRef) throws CoreException {
         jarFile = null;
         if (contentRef instanceof JarContentReference) {
             JarContentReference jarReference = (JarContentReference) contentRef;
             try {
                 jarFile = jarReference.asFile();
                 if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_INSTALL)
                     UpdateCore.debug("Attempting to read JAR file:"+jarFile); //$NON-NLS-1$

                 // # of entries
 if (!jarFile.exists()) throw new IOException ();
                 JarFile jar = new JarFile (jarFile);
                 if (jar !=null){
                     try {
                         jar.close();
                     } catch (IOException ex) {
                         // unchecked
 }
                 }
             } catch (ZipException e){
                 throw Utilities.newCoreException(NLS.bind(Messages.JarVerifier_InvalidJar, (new String [] { jarReference.toString() })), e);
             } catch (IOException e) {
                 throw Utilities.newCoreException(NLS.bind(Messages.JarVerifier_UnableToAccessJar, (new String [] { jarReference.toString() })), e);
             }
         }

         result = new CertVerificationResult();
         result.setVerificationCode(IVerificationResult.UNKNOWN_ERROR);
         result.setResultException(null);
         result.setFeature(feature);
         result.setContentReference(contentRef);
     }

     /*
      * @param newMonitor org.eclipse.core.runtime.IProgressMonitor
      */
     private void setMonitor(IProgressMonitor newMonitor) {
         monitor = newMonitor;
     }

     /*
      * @see IVerifier#verify(IFeature,ContentReference,boolean, InstallMonitor)
      */
     public IVerificationResult verify(
         IFeature feature,
         ContentReference reference,
         boolean isFeatureVerification,
         InstallMonitor monitor)
         throws CoreException {

         if (reference == null)
             return result;

         // if parent knows how to verify, ask the parent first
 if (getParent() != null) {
             IVerificationResult vr =
                 getParent().verify(feature, reference, isFeatureVerification, monitor);
             if (vr.getVerificationCode() != IVerificationResult.TYPE_ENTRY_UNRECOGNIZED)
                 return vr;
         }

         // the parent couldn't verify
 setMonitor(monitor);
         init(feature, reference);
         result.isFeatureVerification(isFeatureVerification);

         if (jarFile!=null) {
             result = verify(jarFile.getAbsolutePath(), reference.getIdentifier());
         } else {
             result.setVerificationCode(IVerificationResult.TYPE_ENTRY_UNRECOGNIZED);
         }

         return result;
     }

     /*
      *
      */
     private CertVerificationResult verify(String file, String identifier) {

         try {
             CertificateVerifier verifier = factory.getVerifier(new File (file));
             // verify integrity
 verifyIntegrity(verifier, identifier);

             //if user already said yes
 result.alreadySeen(alreadyValidated());

             // save the fact the file is not signed, so the user will not be prompted again
 if (result.getVerificationCode()
                 == IVerificationResult.TYPE_ENTRY_NOT_SIGNED) {
                 acceptUnsignedFiles = true;
             }

         } catch (Exception e) {
             result.setVerificationCode(IVerificationResult.UNKNOWN_ERROR);
             result.setResultException(e);
         }

         if (monitor != null) {
             monitor.worked(1);
             if (monitor.isCanceled()) {
                 result.setVerificationCode(IVerificationResult.VERIFICATION_CANCELLED);
             }
         }

         return result;
     }

     /*
      * Verifies the integrity of the JAR
      */
     private void verifyIntegrity(CertificateVerifier verifier, String identifier) {
         try {
             if (verifier.isSigned()) {
                 // If the JAR is signed and invalid then mark as corrupted
 if (verifier.verifyContent().length == 0) {
                     CertificateChain[] roots = verifier.getChains();
                     result.setChains(roots);
                     for (int i = 0; i < roots.length; i++)
                         if (roots[i].isTrusted()) {
                             result.setFoundChain(roots[i]);
                             result.setVerificationCode(IVerificationResult.TYPE_ENTRY_SIGNED_RECOGNIZED);
                             break;
                         }
                     if (result.getVerificationCode() != IVerificationResult.TYPE_ENTRY_SIGNED_RECOGNIZED)
                         result.setVerificationCode(IVerificationResult.TYPE_ENTRY_SIGNED_UNRECOGNIZED);
                 } else
                     result.setVerificationCode(IVerificationResult.TYPE_ENTRY_CORRUPTED);
             } else {
                 result.setVerificationCode(IVerificationResult.TYPE_ENTRY_NOT_SIGNED);
                 return;
             }
         } catch (Exception e) {
             result.setVerificationCode(IVerificationResult.UNKNOWN_ERROR);
             result.setResultException(e);
         }
     }

     /*
      *
      */
     private boolean alreadyValidated() {
         int verifyCode = result.getVerificationCode();
         if (verifyCode == IVerificationResult.TYPE_ENTRY_NOT_SIGNED)
             return (acceptUnsignedFiles);
         if (verifyCode == IVerificationResult.UNKNOWN_ERROR)
             return false;
         if (result.getchains() != null) { //getTrustedCertificates() can't be null as it is lazy initialized
 Iterator iter = getTrustedCertificates().iterator();
             CertificateChain[] roots = result.getchains();

             // check if this is not a user accepted certificate for this feature
 while (iter.hasNext()) {
                 CertificateChain chain = (CertificateChain) iter.next();
                 for (int i = 0; i < roots.length; i++)
                     if (chain.equals(roots[i]))
                         return true;
             }

             // if certificate pair not found in trusted add it for next time
 for (int i = 0; i < roots.length; i++) {
                 addTrustedCertificate(roots[i]);
             }
         }

         return false;
     }

     /*
      *
      */
     private void addTrustedCertificate(CertificateChain chain) {
         if (trustedCertificates == null)
             trustedCertificates = new ArrayList ();
         if (chain != null)
             trustedCertificates.add(chain);
     }

     /*
      *
      */
     private List getTrustedCertificates() {
         if (trustedCertificates == null)
             trustedCertificates = new ArrayList ();
         return trustedCertificates;
     }

     /**
      * @see IVerifier#setParent(IVerifier)
      */
     public void setParent(IVerifier parentVerifier) {
         super.setParent(parentVerifier);
         initialize();
     }

 }

